package middleware

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"mychat/internal/common/cmctx"
	"mychat/internal/common/constants"
	"mychat/internal/common/logger"
)

type bufWriter struct {
	gin.ResponseWriter
	buffer bytes.Buffer
}

func newBufWriter(writer gin.ResponseWriter) *bufWriter {
	return &bufWriter{
		writer,
		bytes.Buffer{},
	}
}

func (bw *bufWriter) Write(content []byte) (int, error) {
	n, err := bw.ResponseWriter.Write(content)
	contentType := bw.Header().Get("Content-Type")
	if strings.Contains(contentType, "application/json") ||
		strings.Contains(contentType, "text/plain") {
		bw.buffer.Write(content)
	}
	return n, err
}

func LoggerHandler(lg logger.Logger) gin.HandlerFunc {
	return func(gtx *gin.Context) {
		requestId := gtx.GetHeader(constants.HeaderRequestId)
		cmctx.SetGtxRequestId(gtx, requestId)
		req := gtx.Request
		req = req.WithContext(cmctx.SetCtxRequestId(req.Context(), requestId))

		logEntry := make(map[string]any)
		logEntry["method"] = req.Method
		logEntry["uri"] = req.URL.Path
		jsHeader, err := json.Marshal(req.Header)
		if err != nil {
			logEntry["req_header_json_err"] = err
			logEntry["req_header"] = "{}"
		} else {
			logEntry["req_header"] = string(jsHeader)
		}
		logEntry["query"] = req.URL.RawQuery
		startTime := time.Now()
		logEntry["start_time"] = startTime
		reqBody, err := io.ReadAll(req.Body)
		if err != nil {
			logEntry["body_read_err"] = err
		} else {
			req.Body.Close()
		}
		req.Body = io.NopCloser(bytes.NewReader(reqBody))
		logEntry["body"] = string(reqBody)
		gtx.Request = req
		buf := newBufWriter(gtx.Writer)
		gtx.Writer = buf
		cmctx.SetGtxLogEntry(gtx, logEntry)

		gtx.Next()

		logEntry = cmctx.GetGtxLogEntry(gtx)

		endTime := time.Now()
		logEntry["end_time"] = endTime
		logEntry["process_time"] = endTime.UnixMilli() - startTime.UnixMilli()
		logEntry["response"] = buf.buffer.String()
		respHeaderJson, err := json.Marshal(gtx.Writer.Header())
		if err != nil {
			logEntry["resp_header_json_err"] = err
			logEntry["resp_header"] = "{}"
		} else {
			logEntry["resp_header"] = string(respHeaderJson)
		}

		respStatusCode := gtx.Writer.Status()
		logEntry["status"] = respStatusCode
		if respStatusCode == http.StatusOK {
			logEntry["logger_level"] = logger.INFO.String()
		} else {
			logEntry["logger_level"] = logger.WARNING.String()
		}

		logEntry["logger_tag"] = string(logger.LoggerTagMiddleware)
		logEntry["_ctx"] = map[string]string{
			"ctx_request_id": cmctx.GetGtxRequestId(gtx),
		}
		lg.LogEntry(logEntry)
	}
}
